﻿//==============================================================================
// File: AccordionControl.cs
// Created: 2010-05-28
// Author: Arek Świerczek
//==============================================================================
// This file is a part of MAMMOTH project.
// Copyright (C) 2010 AGH University of Science and Technology, Krakow.
// https://caribou.iisg.agh.edu.pl/trac/mammoth
//
// Mammoth is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// Mammoth is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public License
// along with Mammoth. If not, see http://www.gnu.org/licenses/.
//==============================================================================

using System;
using System.Windows.Controls.Primitives;
using System.Windows.Controls;
using System.Windows;
using System.ComponentModel;
using System.Collections.Specialized;

namespace WpfRcp.Controls
{
    [StyleTypedProperty(Property = "ItemContainerStyle", StyleTargetType = typeof(AccordionItem))]
    [TemplatePart(Name = "PART_ItemsPresenter", Type = typeof(ItemsPresenter))]
    public class AccordionControl : MultiSelector
    {
        private AccordionItem m_UpperItem;
        private AccordionItem m_LowerItem;

        public AccordionControl()
        {
            SizeChanged += AccordionControl_SizeChanged;
            AddHandler(Thumb.DragStartedEvent, new DragStartedEventHandler(OnDragStarted));
            AddHandler(Thumb.DragDeltaEvent, new DragDeltaEventHandler(OnDragDelta));
            AddHandler(Thumb.DragCompletedEvent, new DragCompletedEventHandler(OnDragCompleted));
        }

        static AccordionControl()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(AccordionControl), new FrameworkPropertyMetadata(typeof(AccordionControl)));

            var defaultValue = new ItemsPanelTemplate(new FrameworkElementFactory(typeof(StackPanel)));
            defaultValue.Seal();
            ItemsPanelProperty.OverrideMetadata(typeof(AccordionControl), new FrameworkPropertyMetadata(defaultValue));
        }

        protected override void OnSelectionChanged(SelectionChangedEventArgs e)
        {
            base.OnSelectionChanged(e);

            if (IsLoaded)
            {
                OnSelect(e);
            }
        }

        protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
        {
            base.OnItemsChanged(e);

            if (IsLoaded)
            {
                OnScale();
            }
        }

        protected override bool IsItemItsOwnContainerOverride(object item)
        {
            return item is AccordionItem || item is AccordionSplitter;
        }

        protected override DependencyObject GetContainerForItemOverride()
        {
            return new AccordionItem();
        }

        private void OnSelect(SelectionChangedEventArgs e)
        {
            foreach (var item in e.AddedItems)
            {
                AccordionItem addedItem = GetAccordionItem(item);
                AccordionItem siblingItem = NextSelectedItem(item);
                FrameworkElement addedHost = GetContentHost(addedItem);

                if (siblingItem != null)
                {
                    FrameworkElement siblingHost = GetContentHost(siblingItem);

                    double stolenSpace = siblingHost.ActualHeight * addedItem.OccupiedRatio;
                    siblingHost.Height = siblingHost.ActualHeight - stolenSpace - addedHost.Margin.Top - addedHost.Margin.Bottom;
                    addedHost.Height = stolenSpace;
                }
                else
                {
                    addedHost.Height = GetAvailableSpace() - addedHost.Margin.Top - addedHost.Margin.Bottom;
                }

                addedHost.Visibility = Visibility.Visible;
            }

            foreach (var item in e.RemovedItems)
            {
                AccordionItem removedItem = GetAccordionItem(item);
                AccordionItem siblingItem = NextSelectedItem(item);
                FrameworkElement removedHost = GetContentHost(removedItem);

                if (siblingItem != null)
                {
                    FrameworkElement siblingHost = GetContentHost(siblingItem);

                    removedItem.OccupiedRatio = removedHost.ActualHeight /
                                                (removedHost.ActualHeight + siblingHost.ActualHeight);
                    siblingHost.Height += removedHost.ActualHeight + removedHost.Margin.Top + removedHost.Margin.Bottom;
                }
                else
                {
                    removedItem.OccupiedRatio = 0.5;
                }

                removedHost.Visibility = Visibility.Collapsed;
            }
        }

        private void OnScale()
        {
            double totalHeight = CalculateOccupiedHeight();
            CollapseItems();
            double availableSpace = GetAvailableSpace();

            double overflow = 0.0;
            foreach (var item in SelectedItems)
            {
                AccordionItem accordionItem = GetAccordionItem(item);
                if (accordionItem.IsSelected)
                {
                    FrameworkElement host = GetContentHost(accordionItem);
                    double height = availableSpace * (accordionItem.OccupiedHeight / totalHeight) - host.Margin.Top - host.Margin.Bottom;
                    
                    height += overflow;

                    if(height < 0.0)
                    {
                        overflow += height;
                        height = 0.0;
                    }
                        
                    host.Height = height;                        
                    host.Visibility = Visibility.Visible;
                }
            }

            var presenter = Template.FindName("PART_ItemsPresenter", this) as ItemsPresenter;
            if (presenter != null) 
                presenter.UpdateLayout();
        }

        private void OnDragCompleted(object sender, DragCompletedEventArgs e)
        {
            if (e.Source is AccordionSplitter)
            {
                m_LowerItem = null;
                m_UpperItem = null;
                e.Handled = true;
            }
        }

        private void OnDragDelta(object sender, DragDeltaEventArgs e)
        {
            if (e.Source is AccordionSplitter)
            {
                if (m_UpperItem != null && m_LowerItem != null)
                {
                    FrameworkElement upperHost = GetContentHost(m_UpperItem);
                    FrameworkElement lowerHost = GetContentHost(m_LowerItem);

                    if (upperHost.Height + e.VerticalChange > 0 && lowerHost.Height - e.VerticalChange > 0)
                    {
                        upperHost.Height += e.VerticalChange;
                        lowerHost.Height += -e.VerticalChange;
                    }
                }
                e.Handled = true;
            }
        }

        private void OnDragStarted(object sender, DragStartedEventArgs e)
        {
            if (e.Source is AccordionSplitter)
            {
                GetSiblingItems(e.Source);
                e.Handled = true;
            }
        }

        #region Helper Methods

        private void GetSiblingItems(object splitterItem)
        {
            bool returnNext = false;
            AccordionItem lastSelectedItem = null;
            foreach (var item in Items)
            {
                AccordionItem someItem = GetAccordionItem(item);
                if (someItem != null && someItem.IsSelected)
                {
                    if (returnNext)
                    {
                        m_LowerItem = someItem;
                        return;
                    }
                    lastSelectedItem = someItem;
                }

                if (item == splitterItem)
                {
                    m_UpperItem = lastSelectedItem;
                    returnNext = true;
                }
            }
        }

        private AccordionItem NextSelectedItem(object previousItem)
        {
            bool returnNext = false;
            AccordionItem lastSelectedItem = null;
            foreach (var item in Items)
            {
                AccordionItem someItem = GetAccordionItem(item);
                if (someItem != null && someItem.IsSelected && item != previousItem)
                {
                    if (returnNext)
                    {
                        return someItem;
                    }
                    lastSelectedItem = someItem;
                }

                if (item == previousItem)
                {
                    returnNext = true;
                }
            }
            return lastSelectedItem;
        }

        private double CalculateOccupiedHeight()
        {
            double totalHeight = 0.0;
            foreach (var item in Items)
            {
                AccordionItem accordionItem = GetAccordionItem(item);
                if (accordionItem != null && accordionItem.IsSelected)
                {
                    FrameworkElement host = GetContentHost(accordionItem);
                    accordionItem.OccupiedHeight = host.ActualHeight + host.Margin.Top + host.Margin.Bottom;
                    totalHeight += accordionItem.OccupiedHeight;
                }
            }
            return totalHeight;
        }

        private double GetAvailableSpace()
        {
            double reservedSpace = 0.0;
            foreach (var item in Items)
            {
                AccordionItem accordionItem = GetAccordionItem(item);
                if (accordionItem != null)
                {
                    reservedSpace += accordionItem.ActualHeight + accordionItem.Margin.Top + accordionItem.Margin.Bottom;
                }
                else
                {
                    var accordionSplitter = (AccordionSplitter)item;
                    reservedSpace += accordionSplitter.ActualHeight + accordionSplitter.Margin.Top + accordionSplitter.Margin.Bottom;
                }
            }

            var presenter = (ItemsPresenter)Template.FindName("PART_ItemsPresenter", this);

            double availableSpace = presenter.ActualHeight - reservedSpace;
            if (availableSpace < 0.0)
                availableSpace = 0.0;

            return availableSpace;
        }

        private void CollapseItems()
        {
            foreach (var item in Items)
            {
                AccordionItem accordionItem = GetAccordionItem(item);
                if (accordionItem != null)
                {
                    FrameworkElement host = GetContentHost(accordionItem);
                    host.Visibility = Visibility.Collapsed;
                }
            }

            var presenter = Template.FindName("PART_ItemsPresenter", this) as ItemsPresenter;
            if (presenter != null)
                presenter.UpdateLayout();
        }

        private static FrameworkElement GetContentHost(AccordionItem item)
        {
            var contentPresenter = item.Template.FindName("PART_ContentHost", item) as FrameworkElement;
            return contentPresenter;
        }

        private AccordionItem GetAccordionItem(object selectedItem)
        {
            if (selectedItem == null)
                return null;
            
            return selectedItem as AccordionItem ??
                   ItemContainerGenerator.ContainerFromItem(selectedItem) as AccordionItem;
        }

        private void AccordionControl_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            OnScale();
        }

        #endregion
    }

    [DefaultEvent("Selected")]
    [TemplatePart(Name = "PART_ContentHost", Type = typeof(FrameworkElement))]
    public class AccordionItem : HeaderedContentControl
    {
        public static readonly DependencyProperty IsSelectedProperty = Selector.IsSelectedProperty.AddOwner(typeof(AccordionItem), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.Journal | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnIsSelectedChanged));
        public static readonly RoutedEvent SelectedEvent = Selector.SelectedEvent.AddOwner(typeof(AccordionItem));
        public static readonly RoutedEvent UnselectedEvent = Selector.UnselectedEvent.AddOwner(typeof(AccordionItem));

        public AccordionItem()
        {
            OccupiedRatio = 0.5;
        }

        static AccordionItem()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(AccordionItem), new FrameworkPropertyMetadata(typeof(AccordionItem)));
        }

        // Events
        public event RoutedEventHandler Selected
        {
            add { AddHandler(SelectedEvent, value); }
            remove { RemoveHandler(SelectedEvent, value); }
        }

        public event RoutedEventHandler Unselected
        {
            add { AddHandler(UnselectedEvent, value); }
            remove { RemoveHandler(UnselectedEvent, value); }
        }

        [Bindable(true), Category("Appearance")]
        public bool IsSelected
        {
            get { return (bool)GetValue(IsSelectedProperty); }
            set { SetValue(IsSelectedProperty, value); }
        }

        internal double OccupiedHeight { set; get; }
        internal double OccupiedRatio { set; get; }

        private static void OnIsSelectedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var container = (AccordionItem)d;
            var newValue = (bool)e.NewValue;

            if (newValue)
            {
                container.RaiseEvent(new RoutedEventArgs(Selector.SelectedEvent, container));
            }
            else
            {
                container.RaiseEvent(new RoutedEventArgs(Selector.UnselectedEvent, container));
            }
        }
    }

    public class AccordionSplitter : Thumb
    {
        static AccordionSplitter()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(AccordionSplitter), new FrameworkPropertyMetadata(typeof(AccordionSplitter)));
        }
    }
}